//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.core
{

	using Node = org.neo4j.graphdb.Node;
	using NotFoundException = org.neo4j.graphdb.NotFoundException;
	using Relationship = org.neo4j.graphdb.Relationship;
	using RelationshipType = org.neo4j.graphdb.RelationshipType;
	using PropertyData = org.neo4j.kernel.impl.nioneo.store.PropertyData;
	using LockException = org.neo4j.kernel.impl.transaction.LockException;
	using LockType = org.neo4j.kernel.impl.transaction.LockType;
	using ArrayMap = org.neo4j.kernel.impl.util.ArrayMap;

	internal class RelationshipImpl : NeoPrimitive, Relationship, Comparable<Relationship>
	{
		private readonly int startNodeId;
		private readonly int endNodeId;
		private readonly RelationshipType type;

	// Dummy constructor for NodeManager to acquire read lock on relationship
	// when loading from PL.
		internal RelationshipImpl(int id, NodeManager nodeManager) : base(id, nodeManager)
		{
			this.startNodeId = -1;
			this.endNodeId = -1;
			this.type = null;
		}

		internal RelationshipImpl(int id, int startNodeId, int endNodeId, RelationshipType type, bool newRel, NodeManager nodeManager) : base(id, newRel, nodeManager)
		{
			if (type == null)
			{
				throw new IllegalArgumentException("Null type");
			}
			if (startNodeId == endNodeId)
			{
				throw new IllegalArgumentException("Start node equals end node");
			}

			this.startNodeId = startNodeId;
			this.endNodeId = endNodeId;
			this.type = type;
		}

		protected internal override void changeProperty(int propertyId, object @value)
		{
			nodeManager.relChangeProperty(this, propertyId, @value);
		}

		protected internal override int addProperty(PropertyIndex index, object @value)
		{
			return nodeManager.relAddProperty(this, index, @value);
		}

		protected internal override void removeProperty(int propertyId)
		{
			nodeManager.relRemoveProperty(this, propertyId);
		}

		protected internal override ArrayMap<int, PropertyData> loadProperties()
		{
			return nodeManager.loadProperties(this);
		}

		public virtual Node[] getNodes()
		{
			return new Node[] { new NodeProxy(startNodeId, nodeManager), new NodeProxy(endNodeId, nodeManager) };
		}

		public virtual Node getOtherNode(Node node)
		{
			if (startNodeId == (int) node.getId())
			{
				return new NodeProxy(endNodeId, nodeManager);
			}
			if (endNodeId == (int) node.getId())
			{
				return new NodeProxy(startNodeId, nodeManager);
			}
			throw new NotFoundException("Node[" + node.getId() + "] not connected to this relationship[" + getId() + "]");
		}

		public virtual Node getStartNode()
		{
			return new NodeProxy(startNodeId, nodeManager);
		}

		internal virtual int getStartNodeId()
		{
			return startNodeId;
		}

		public virtual Node getEndNode()
		{
			return new NodeProxy(endNodeId, nodeManager);
		}

		internal virtual int getEndNodeId()
		{
			return endNodeId;
		}

		public virtual RelationshipType getType()
		{
			return type;
		}

		public virtual bool isType(RelationshipType otherType)
		{
			return otherType != null && otherType.name().Equals(this.getType().name());
		}

		public virtual void delete()
		{
			NodeImpl startNode = null;
			NodeImpl endNode = null;
			bool startNodeLocked = false;
			bool endNodeLocked = false;
			nodeManager.acquireLock(this, LockType.WRITE);
			bool success = false;
			try
			{
				startNode = nodeManager.getLightNode(startNodeId);
				if (startNode != null)
				{
					nodeManager.acquireLock(startNode, LockType.WRITE);
					startNodeLocked = true;
				}
				endNode = nodeManager.getLightNode(endNodeId);
				if (endNode != null)
				{
					nodeManager.acquireLock(endNode, LockType.WRITE);
					endNodeLocked = true;
				}
			// no need to load full relationship, all properties will be
			// deleted when relationship is deleted

				nodeManager.deleteRelationship(this);
				if (startNode != null)
				{
					startNode.removeRelationship(type, id);
				}
				if (endNode != null)
				{
					endNode.removeRelationship(type, id);
				}
				success = true;
			}
			finally
			{
				bool releaseFailed = false;
				try
				{
					if (startNodeLocked)
					{
						nodeManager.releaseLock(startNode, LockType.WRITE);
					}
				}
				catch (Exception e)
				{
					releaseFailed = true;
					e.printStackTrace();
				}
				try
				{
					if (endNodeLocked)
					{
						nodeManager.releaseLock(endNode, LockType.WRITE);
					}
				}
				catch (Exception e)
				{
					releaseFailed = true;
					e.printStackTrace();
				}
				nodeManager.releaseLock(this, LockType.WRITE);
				if (!success)
				{
					setRollbackOnly();
				}
				if (releaseFailed)
				{
					throw new LockException("Unable to release locks [" + startNode + "," + endNode + "] in relationship delete->" + this);
				}
			}
		}

		public override string ToString()
		{
			return "RelationshipImpl #" + this.getId() + " of type " + type + " between Node[" + startNodeId + "] and Node[" + endNodeId + "]";
		}

		public virtual int compareTo(Relationship r)
		{
			int ourId = (int) this.getId(), theirId = (int) r.getId();

			if (ourId < theirId)
			{
				return -1;
			}
			else if (ourId > theirId)
			{
				return 1;
			}
			else
			{
				return 0;
			}
		}

///    
///     <summary> * Returns true if object <CODE>o</CODE> is a relationship with the same
///     * id as <CODE>this</CODE>.
///     *  </summary>
///     * <param name="o">
///     *            the object to compare </param>
///     * <returns> true if equal, else false </returns>
///     
		public override bool Equals(object o)
		{
		// verify type and not null, should use Node inteface
			if (!(o is Relationship))
			{
				return false;
			}

		// The equals contract:
		// o reflexive: x.equals(x)
		// o symmetric: x.equals(y) == y.equals(x)
		// o transitive: ( x.equals(y) && y.equals(z) ) == true
		// then x.equals(z) == true
		// o consistent: the nodeId never changes
			return this.getId() == ((Relationship) o).getId();

		}

		public override int GetHashCode()
		{
			return id;
		}
	}
}